/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: LiteralElement.java,v 1.2.4.1 2005/09/13 12:38:33 pvedula Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.compiler;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.PUSH;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ErrorMsg;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;

import com.sun.org.apache.xml.internal.serializer.ElemDesc;
import com.sun.org.apache.xml.internal.serializer.ToHTMLStream;

/**
 * @author Jacek Ambroziak
 * @author Santiago Pericas-Geertsen
 * @author Morten Jorgensen
 */
final class LiteralElement extends Instruction {

  private String _name;
  private LiteralElement _literalElemParent = null;
  private Vector _attributeElements = null;
  private Hashtable _accessedPrefixes = null;

  // True if all attributes of this LRE are unique, i.e. they all have
  // different names. This flag is set to false if some attribute
  // names are not known at compile time.
  private boolean _allAttributesUnique = false;

  private final static String XMLNS_STRING = "xmlns";

  /**
   * Returns the QName for this literal element
   */
  public QName getName() {
    return _qname;
  }

  /**
   * Displays the contents of this literal element
   */
  public void display(int indent) {
    indent(indent);
    Util.println("LiteralElement name = " + _name);
    displayContents(indent + IndentIncrement);
  }

  /**
   * Returns the namespace URI for which a prefix is pointing to
   */
  private String accessedNamespace(String prefix) {
    if (_literalElemParent != null) {
      String result = _literalElemParent.accessedNamespace(prefix);
      if (result != null) {
        return result;
      }
    }
    return _accessedPrefixes != null ?
        (String) _accessedPrefixes.get(prefix) : null;
  }

  /**
   * Method used to keep track of what namespaces that are references by
   * this literal element and its attributes. The output must contain a
   * definition for each namespace, so we stuff them in a hashtable.
   */
  public void registerNamespace(String prefix, String uri,
      SymbolTable stable, boolean declared) {

    // Check if the parent has a declaration for this namespace
    if (_literalElemParent != null) {
      final String parentUri = _literalElemParent.accessedNamespace(prefix);
      if (parentUri != null && parentUri.equals(uri)) {
        return;
      }
    }

    // Check if we have any declared namesaces
    if (_accessedPrefixes == null) {
      _accessedPrefixes = new Hashtable();
    } else {
      if (!declared) {
        // Check if this node has a declaration for this namespace
        final String old = (String) _accessedPrefixes.get(prefix);
        if (old != null) {
          if (old.equals(uri)) {
            return;
          } else {
            prefix = stable.generateNamespacePrefix();
          }
        }
      }
    }

    if (!prefix.equals("xml")) {
      _accessedPrefixes.put(prefix, uri);
    }
  }

  /**
   * Translates the prefix of a QName according to the rules set in
   * the attributes of xsl:stylesheet. Also registers a QName to assure
   * that the output element contains the necessary namespace declarations.
   */
  private String translateQName(QName qname, SymbolTable stable) {
    // Break up the QName and get prefix:localname strings
    String localname = qname.getLocalPart();
    String prefix = qname.getPrefix();

    // Treat default namespace as "" and not null
    if (prefix == null) {
      prefix = Constants.EMPTYSTRING;
    } else if (prefix.equals(XMLNS_STRING)) {
      return (XMLNS_STRING);
    }

    // Check if we must translate the prefix
    final String alternative = stable.lookupPrefixAlias(prefix);
    if (alternative != null) {
      stable.excludeNamespaces(prefix);
      prefix = alternative;
    }

    // Get the namespace this prefix refers to
    String uri = lookupNamespace(prefix);
    if (uri == null) {
      return (localname);
    }

    // Register the namespace as accessed
    registerNamespace(prefix, uri, stable, false);

    // Construct the new name for the element (may be unchanged)
    if (prefix != Constants.EMPTYSTRING) {
      return (prefix + ":" + localname);
    } else {
      return (localname);
    }
  }

  /**
   * Add an attribute to this element
   */
  public void addAttribute(SyntaxTreeNode attribute) {
    if (_attributeElements == null) {
      _attributeElements = new Vector(2);
    }
    _attributeElements.add(attribute);
  }

  /**
   * Set the first attribute of this element
   */
  public void setFirstAttribute(SyntaxTreeNode attribute) {
    if (_attributeElements == null) {
      _attributeElements = new Vector(2);
    }
    _attributeElements.insertElementAt(attribute, 0);
  }

  /**
   * Type-check the contents of this element. The element itself does not
   * need any type checking as it leaves nothign on the JVM's stack.
   */
  public Type typeCheck(SymbolTable stable) throws TypeCheckError {
    // Type-check all attributes
    if (_attributeElements != null) {
      final int count = _attributeElements.size();
      for (int i = 0; i < count; i++) {
        SyntaxTreeNode node =
            (SyntaxTreeNode) _attributeElements.elementAt(i);
        node.typeCheck(stable);
      }
    }
    typeCheckContents(stable);
    return Type.Void;
  }

  /**
   * This method starts at a given node, traverses all namespace mappings,
   * and assembles a list of all prefixes that (for the given node) maps
   * to _ANY_ namespace URI. Used by literal result elements to determine
   */
  public Enumeration getNamespaceScope(SyntaxTreeNode node) {
    Hashtable all = new Hashtable();

    while (node != null) {
      Hashtable mapping = node.getPrefixMapping();
      if (mapping != null) {
        Enumeration prefixes = mapping.keys();
        while (prefixes.hasMoreElements()) {
          String prefix = (String) prefixes.nextElement();
          if (!all.containsKey(prefix)) {
            all.put(prefix, mapping.get(prefix));
          }
        }
      }
      node = node.getParent();
    }
    return (all.keys());
  }

  /**
   * Determines the final QName for the element and its attributes.
   * Registers all namespaces that are used by the element/attributes
   */
  public void parseContents(Parser parser) {
    final SymbolTable stable = parser.getSymbolTable();
    stable.setCurrentNode(this);

    // Check if in a literal element context
    SyntaxTreeNode parent = getParent();
    if (parent != null && parent instanceof LiteralElement) {
      _literalElemParent = (LiteralElement) parent;
    }

    _name = translateQName(_qname, stable);

    // Process all attributes and register all namespaces they use
    final int count = _attributes.getLength();
    for (int i = 0; i < count; i++) {
      final QName qname = parser.getQName(_attributes.getQName(i));
      final String uri = qname.getNamespace();
      final String val = _attributes.getValue(i);

      // Handle xsl:use-attribute-sets. Attribute sets are placed first
      // in the vector or attributes to make sure that later local
      // attributes can override an attributes in the set.
      if (qname.equals(parser.getUseAttributeSets())) {
        if (!Util.isValidQNames(val)) {
          ErrorMsg err = new ErrorMsg(ErrorMsg.INVALID_QNAME_ERR, val, this);
          parser.reportError(Constants.ERROR, err);
        }
        setFirstAttribute(new UseAttributeSets(val, parser));
      }
      // Handle xsl:extension-element-prefixes
      else if (qname.equals(parser.getExtensionElementPrefixes())) {
        stable.excludeNamespaces(val);
      }
      // Handle xsl:exclude-result-prefixes
      else if (qname.equals(parser.getExcludeResultPrefixes())) {
        stable.excludeNamespaces(val);
      } else {
        // Ignore special attributes (e.g. xmlns:prefix and xmlns)
        final String prefix = qname.getPrefix();
        if (prefix != null && prefix.equals(XMLNS_PREFIX) ||
            prefix == null && qname.getLocalPart().equals("xmlns") ||
            uri != null && uri.equals(XSLT_URI)) {
          continue;
        }

        // Handle all other literal attributes
        final String name = translateQName(qname, stable);
        LiteralAttribute attr = new LiteralAttribute(name, val, parser, this);
        addAttribute(attr);
        attr.setParent(this);
        attr.parseContents(parser);
      }
    }

    // Register all namespaces that are in scope, except for those that
    // are listed in the xsl:stylesheet element's *-prefixes attributes
    final Enumeration include = getNamespaceScope(this);
    while (include.hasMoreElements()) {
      final String prefix = (String) include.nextElement();
      if (!prefix.equals("xml")) {
        final String uri = lookupNamespace(prefix);
        if (uri != null && !stable.isExcludedNamespace(uri)) {
          registerNamespace(prefix, uri, stable, true);
        }
      }
    }

    parseChildren(parser);

    // Process all attributes and register all namespaces they use
    for (int i = 0; i < count; i++) {
      final QName qname = parser.getQName(_attributes.getQName(i));
      final String val = _attributes.getValue(i);

      // Handle xsl:extension-element-prefixes
      if (qname.equals(parser.getExtensionElementPrefixes())) {
        stable.unExcludeNamespaces(val);
      }
      // Handle xsl:exclude-result-prefixes
      else if (qname.equals(parser.getExcludeResultPrefixes())) {
        stable.unExcludeNamespaces(val);
      }
    }
  }

  protected boolean contextDependent() {
    return dependentContents();
  }

  /**
   * Compiles code that emits the literal element to the output handler,
   * first the start tag, then namespace declaration, then attributes,
   * then the element contents, and then the element end tag. Since the
   * value of an attribute may depend on a variable, variables must be
   * compiled first.
   */
  public void translate(ClassGenerator classGen, MethodGenerator methodGen) {

    final ConstantPoolGen cpg = classGen.getConstantPool();
    final InstructionList il = methodGen.getInstructionList();

    // Check whether all attributes are unique.
    _allAttributesUnique = checkAttributesUnique();

    // Compile code to emit element start tag
    il.append(methodGen.loadHandler());

    il.append(new PUSH(cpg, _name));
    il.append(DUP2);                // duplicate these 2 args for endElement
    il.append(methodGen.startElement());

    // The value of an attribute may depend on a (sibling) variable
    int j = 0;
    while (j < elementCount()) {
      final SyntaxTreeNode item = (SyntaxTreeNode) elementAt(j);
      if (item instanceof Variable) {
        item.translate(classGen, methodGen);
      }
      j++;
    }

    // Compile code to emit namespace attributes
    if (_accessedPrefixes != null) {
      boolean declaresDefaultNS = false;
      Enumeration e = _accessedPrefixes.keys();

      while (e.hasMoreElements()) {
        final String prefix = (String) e.nextElement();
        final String uri = (String) _accessedPrefixes.get(prefix);

        if (uri != Constants.EMPTYSTRING ||
            prefix != Constants.EMPTYSTRING) {
          if (prefix == Constants.EMPTYSTRING) {
            declaresDefaultNS = true;
          }
          il.append(methodGen.loadHandler());
          il.append(new PUSH(cpg, prefix));
          il.append(new PUSH(cpg, uri));
          il.append(methodGen.namespace());
        }
      }

            /*
             * If our XslElement parent redeclares the default NS, and this
             * element doesn't, it must be redeclared one more time.
             */
      if (!declaresDefaultNS && (_parent instanceof XslElement)
          && ((XslElement) _parent).declaresDefaultNS()) {
        il.append(methodGen.loadHandler());
        il.append(new PUSH(cpg, Constants.EMPTYSTRING));
        il.append(new PUSH(cpg, Constants.EMPTYSTRING));
        il.append(methodGen.namespace());
      }
    }

    // Output all attributes
    if (_attributeElements != null) {
      final int count = _attributeElements.size();
      for (int i = 0; i < count; i++) {
        SyntaxTreeNode node =
            (SyntaxTreeNode) _attributeElements.elementAt(i);
        if (!(node instanceof XslAttribute)) {
          node.translate(classGen, methodGen);
        }
      }
    }

    // Compile code to emit attributes and child elements
    translateContents(classGen, methodGen);

    // Compile code to emit element end tag
    il.append(methodGen.endElement());
  }

  /**
   * Return true if the output method is html.
   */
  private boolean isHTMLOutput() {
    return getStylesheet().getOutputMethod() == Stylesheet.HTML_OUTPUT;
  }

  /**
   * Return the ElemDesc object for an HTML element.
   * Return null if the output method is not HTML or this is not a
   * valid HTML element.
   */
  public ElemDesc getElemDesc() {
    if (isHTMLOutput()) {
      return ToHTMLStream.getElemDesc(_name);
    } else {
      return null;
    }
  }

  /**
   * Return true if all attributes of this LRE have unique names.
   */
  public boolean allAttributesUnique() {
    return _allAttributesUnique;
  }

  /**
   * Check whether all attributes are unique.
   */
  private boolean checkAttributesUnique() {
    boolean hasHiddenXslAttribute = canProduceAttributeNodes(this, true);
    if (hasHiddenXslAttribute) {
      return false;
    }

    if (_attributeElements != null) {
      int numAttrs = _attributeElements.size();
      Hashtable attrsTable = null;
      for (int i = 0; i < numAttrs; i++) {
        SyntaxTreeNode node = (SyntaxTreeNode) _attributeElements.elementAt(i);

        if (node instanceof UseAttributeSets) {
          return false;
        } else if (node instanceof XslAttribute) {
          if (attrsTable == null) {
            attrsTable = new Hashtable();
            for (int k = 0; k < i; k++) {
              SyntaxTreeNode n = (SyntaxTreeNode) _attributeElements.elementAt(k);
              if (n instanceof LiteralAttribute) {
                LiteralAttribute literalAttr = (LiteralAttribute) n;
                attrsTable.put(literalAttr.getName(), literalAttr);
              }
            }
          }

          XslAttribute xslAttr = (XslAttribute) node;
          AttributeValue attrName = xslAttr.getName();
          if (attrName instanceof AttributeValueTemplate) {
            return false;
          } else if (attrName instanceof SimpleAttributeValue) {
            SimpleAttributeValue simpleAttr = (SimpleAttributeValue) attrName;
            String name = simpleAttr.toString();
            if (name != null && attrsTable.get(name) != null) {
              return false;
            } else if (name != null) {
              attrsTable.put(name, xslAttr);
            }
          }
        }
      }
    }
    return true;
  }

  /**
   * Return true if the instructions under the given SyntaxTreeNode can produce attribute nodes
   * to an element. Only return false when we are sure that no attribute node is produced.
   * Return true if we are not sure. If the flag ignoreXslAttribute is true, the direct
   * <xsl:attribute> children of the current node are not included in the check.
   */
  private boolean canProduceAttributeNodes(SyntaxTreeNode node, boolean ignoreXslAttribute) {
    Vector contents = node.getContents();
    int size = contents.size();
    for (int i = 0; i < size; i++) {
      SyntaxTreeNode child = (SyntaxTreeNode) contents.elementAt(i);
      if (child instanceof Text) {
        Text text = (Text) child;
        if (text.isIgnore()) {
          continue;
        } else {
          return false;
        }
      }
      // Cannot add an attribute to an element after children have been added to it.
      // We can safely return false when the instruction can produce an output node.
      else if (child instanceof LiteralElement
          || child instanceof ValueOf
          || child instanceof XslElement
          || child instanceof Comment
          || child instanceof Number
          || child instanceof ProcessingInstruction) {
        return false;
      } else if (child instanceof XslAttribute) {
        if (ignoreXslAttribute) {
          continue;
        } else {
          return true;
        }
      }
      // In general, there is no way to check whether <xsl:call-template> or
      // <xsl:apply-templates> can produce attribute nodes. <xsl:copy> and
      // <xsl:copy-of> can also copy attribute nodes to an element. Return
      // true in those cases to be safe.
      else if (child instanceof CallTemplate
          || child instanceof ApplyTemplates
          || child instanceof Copy
          || child instanceof CopyOf) {
        return true;
      } else if ((child instanceof If
          || child instanceof ForEach)
          && canProduceAttributeNodes(child, false)) {
        return true;
      } else if (child instanceof Choose) {
        Vector chooseContents = child.getContents();
        int num = chooseContents.size();
        for (int k = 0; k < num; k++) {
          SyntaxTreeNode chooseChild = (SyntaxTreeNode) chooseContents.elementAt(k);
          if (chooseChild instanceof When || chooseChild instanceof Otherwise) {
            if (canProduceAttributeNodes(chooseChild, false)) {
              return true;
            }
          }
        }
      }
    }
    return false;
  }

}
